package com.cloudfun.campusshare.service.impl;


import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.request.AlipayOpenAuthTokenAppRequest;
import com.alipay.api.request.AlipaySystemOauthTokenRequest;
import com.alipay.api.response.AlipayOpenAuthTokenAppResponse;
import com.alipay.api.response.AlipaySystemOauthTokenResponse;
import com.aliyun.openservices.iot.api.util.StringUtil;
import com.cloudfun.campusshare.common.constant.*;
import com.cloudfun.campusshare.common.constant.miniprogram.WeChatErrorCode;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.Token;
import com.cloudfun.campusshare.common.model.dto.*;
import com.cloudfun.campusshare.common.model.vo.RegisterVO;
import com.cloudfun.campusshare.common.model.vo.SessionVO;
import com.cloudfun.campusshare.common.response.BaseResult;
import com.cloudfun.campusshare.common.response.BaseResultFactory;
import com.cloudfun.campusshare.configuration.property.AliPayProperty;
import com.cloudfun.campusshare.entity.UserAuthEntity;
import com.cloudfun.campusshare.entity.UserEntity;
import com.cloudfun.campusshare.repo.UserEntityRepo;
import com.cloudfun.campusshare.service.*;
import com.cloudfun.campusshare.util.DateUtil;
import com.cloudfun.campusshare.util.TokenUtil;
import com.cloudfun.campusshare.util.tencent.wechat.WeChatUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;

@Slf4j
@Service
public class SessionServiceImpl implements SessionService {

    @Autowired
    private UserEntityRepo userEntityRepo;

    @Autowired
    private TokenFactory tokenFactory;

    @Autowired
    private UserAuthService userAuthService;

    @Autowired
    private UserService userService;

    @Autowired
    private WeChatService weChatService;

    @Autowired
    private VerifyCodeService verifyCodeService;

    @Autowired
    private BusinessConstant businessConstant;

    @Autowired
    private AliPayProperty aliPayProperty;

    @Override
    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public BaseResult<?> loginWithPassword(SessionVO sessionVO, HttpServletRequest request, HttpServletResponse response) {
        String phone = sessionVO.getPhone();
        String password = sessionVO.getPassword();
        String captcha = sessionVO.getCaptcha();
        if (StringUtil.isNotEmpty(captcha)) {
            boolean validCaptcha = verifyCodeService.verifyCaptcha(request, VerifyType.LOGIN, captcha);
            if (!validCaptcha) {
                return BaseResultFactory.produceResult(Code.ERROR, "验证码错误");
            }
        }
        // 验证captcha
        BaseResult<String> baseResult = new BaseResult<>();
        UserAuthEntity userAuthEntity = userAuthService.getByIdentityTypeAndIdentifier(IdentityType.PHONE, phone);
        // 验证账户是否存在
        if (ObjectUtils.isEmpty(userAuthEntity) || ObjectUtils.isEmpty(userAuthEntity.getUser())) {
            return BaseResultFactory.produceEmptyResult(Code.INVALID_USER);
        }
        UserEntity userEntity = userAuthEntity.getUser();
        Code resultCode = checkAccountAndPWD(userAuthEntity, password);
        long curTime = System.currentTimeMillis();
        switch (resultCode) {
            case SUCCESS:
                // 判断账号是否被冻结
                if (userEntity.getStatus() == AccountStatus.FROZEN.getCode()) {
                    return BaseResultFactory.produceEmptyResult(Code.ACCOUNT_FROZEN);
                }
                // 验证成功
                Token token = new Token();
                token.setPubKey(userEntity.getAddress());
                token.setUserId(userEntity.getId());
                token.setIdentityType(IdentityType.PHONE);
                // 设置过期时间
                long exp = curTime + businessConstant.TOKEN_VALID_TIME * 60000;
                token.setTimestamp(exp);
                String jwtStr = tokenFactory.createJwtWithSecret(token, userAuthEntity.getCredential());
                TokenUtil.setTokenToCookie(jwtStr, true, request, response);
                baseResult.returnWithValue(Code.SUCCESS, jwtStr);
                // 错误次数清空
                userEntity.setErrorCount(0);
                if (userEntity.getStatus().equals(AccountStatus.LOCK.getCode())) {
                    // 设置用户状态为有效
                    userEntity.setStatus(AccountStatus.VALID.getCode());
                }
                break;
            case ERROR_PASSWORD_ACCOUNT:
                baseResult.returnWithoutValue(Code.ERROR_PASSWORD_ACCOUNT);
                int curErrorTime = userEntity.getErrorCount() + 1;
                // 设置密码错误次数
                userEntity.setErrorCount(curErrorTime);
                break;
            case PASSWORD_ERROR_TIME_OVER:
                // 错误次数超过限制
                baseResult.returnWithoutValue(Code.PASSWORD_ERROR_TIME_OVER);
                // 设置锁定时间
                userEntity.setLockTime(String.valueOf(System.currentTimeMillis()));
                // 设置用户状态为锁定
                userEntity.setStatus(AccountStatus.LOCK.getCode());
                break;
            case ACCOUNT_STILL_LOCK:
                // 账户仍处于锁定状态
                // 清空用户密码错误次数
                userEntity.setErrorCount(0);

                baseResult.returnWithoutValue(Code.ACCOUNT_STILL_LOCK);
                break;
            case ACCOUNT_DISABLED:
                baseResult.returnWithoutValue(Code.ACCOUNT_DISABLED);
                break;
            default:
                baseResult.returnWithoutValue(Code.UNKNOWN_ABNORMAL);
                break;
        }
        // 更新账户状态
        userEntityRepo.save(userEntity);
        return baseResult;
    }

    @Override
    @Transactional
    public BaseResult<?> loginByWeChat(WeChatLoginDTO loginDTO, HttpServletRequest request, HttpServletResponse response) {
        WeChatCode2SessionRespDTO sessionResult = weChatService.jsCode2SessionInfo(loginDTO.getCode());
        if (!StringUtils.isEmpty(sessionResult.getErrorCode()) && !WeChatErrorCode.OK.getCode().equals(sessionResult.getErrorCode())) {
            log.error("请求微信登录凭证校验接口失败。错误码：{}，错误信息：{}。", sessionResult.getErrorCode(), sessionResult.getErrorMsg());
            return BaseResultFactory.produceEmptyResult(Code.ERROR);
        }
        String openid = sessionResult.getOpenId();
        String sessionKey = sessionResult.getSessionKey();

        UserAuthDTO openIdUserAuth = new UserAuthDTO();
        openIdUserAuth.setIdentityType(IdentityType.WE_CHAT);
        openIdUserAuth.setIdentifier(openid);
        openIdUserAuth.setCredential(sessionKey);

        if (!StringUtils.isEmpty(loginDTO.getEncryptedData()) && !StringUtils.isEmpty(loginDTO.getIv())) {
            String decrypt = WeChatUtil.decrypt(sessionResult.getSessionKey(), loginDTO.getEncryptedData(), loginDTO.getIv());
            JSONObject jsonObject = JSONUtil.parseObj(decrypt);
            String phone = jsonObject.getStr("purePhoneNumber");
            String userId = this.registerOnNull(phone, openIdUserAuth);
            openIdUserAuth.setUserId(userId);
        } else {
            String userId = userAuthService.updateByIdentifier(openIdUserAuth);
            openIdUserAuth.setUserId(userId);
        }

        String token = this.login(openIdUserAuth, request, response);
        return BaseResultFactory.produceResult(Code.SUCCESS, token);
    }

    private String registerOnNull(String phone, UserAuthDTO userAuthDTO) {
        RegisterVO customerRegisterVO = new RegisterVO();
        customerRegisterVO.setPhone(phone);
        customerRegisterVO.setUserName(phone);
        customerRegisterVO.setRoleType(RoleType.CUSTOMER);
        return userService.register(customerRegisterVO, userAuthDTO);
    }

    @Override
    @Transactional
    public BaseResult<?> loginByVerifyCode(VerifyCodeLoginDTO loginDTO, HttpServletRequest request, HttpServletResponse response) {
        String inputCode = loginDTO.getInputCode();
        String phone = loginDTO.getPhone();
        // 判断手机验证码
        BaseResult<?> result = verifyCodeService.verifyVerifyCode(VerifyType.LOGIN, phone, inputCode);
        if (result.getCode() != Code.SUCCESS.getCode()) {
            return result;
        }
        WeChatCode2SessionRespDTO sessionResult = weChatService.jsCode2SessionInfo(loginDTO.getCode());
        if (!StringUtils.isEmpty(sessionResult.getErrorCode()) && !WeChatErrorCode.OK.getCode().equals(sessionResult.getErrorCode())) {
            log.error("请求微信登录凭证校验接口失败。错误码：{}，错误信息：{}。", sessionResult.getErrorCode(), sessionResult.getErrorMsg());
            return BaseResultFactory.produceEmptyResult(Code.ERROR);
        }

        UserAuthDTO phoneUserAuthDTO = new UserAuthDTO();
        phoneUserAuthDTO.setIdentityType(IdentityType.PHONE);
        phoneUserAuthDTO.setIdentifier(phone);
        phoneUserAuthDTO.setCredential(null);

        this.registerOnNull(phone, phoneUserAuthDTO);
        userAuthService.updateByIdentifier(phoneUserAuthDTO);

        UserAuthDTO openIdUserAuthDTO = new UserAuthDTO();
        openIdUserAuthDTO.setIdentityType(IdentityType.WE_CHAT);
        String openId = sessionResult.getOpenId();
        openIdUserAuthDTO.setIdentifier(openId);
        openIdUserAuthDTO.setCredential(null);
        userAuthService.create(phoneUserAuthDTO);
        String token = this.login(phoneUserAuthDTO, request, response);
        return BaseResultFactory.produceResult(Code.SUCCESS, token);
    }

    @Override
    public BaseResult<?> loginByVerifyCode4H5(VerifyCodeH5LoginDTO loginDTO, HttpServletRequest request, HttpServletResponse response) {
        String inputCode = loginDTO.getInputCode();
        String phone = loginDTO.getPhone();
        // 判断手机验证码
        BaseResult<?> result = verifyCodeService.verifyVerifyCode(VerifyType.LOGIN, phone, inputCode);
        if (result.getCode() != Code.SUCCESS.getCode()) {
            return result;
        }
        UserAuthDTO phoneUserAuthDTO = new UserAuthDTO();
        phoneUserAuthDTO.setIdentityType(IdentityType.PHONE);
        phoneUserAuthDTO.setIdentifier(phone);
        phoneUserAuthDTO.setCredential(null);

        String userId = this.registerOnNull(phone, phoneUserAuthDTO);
        userAuthService.updateByIdentifier(phoneUserAuthDTO);



        if(LoginSource.WECHAT.equals(loginDTO.getSource())){
            //验证微信code，获取openid
            UserAuthEntity weChatOfficialAuth = userAuthService.getByUserIdAndType(userId, IdentityType.WE_CHAT_OFFICIAL_ACCOUNT);
            if(Objects.isNull(weChatOfficialAuth)){
                String openId = weChatService.getOpenId4OfficialAccount(loginDTO.getCode());
                UserAuthDTO officialAuthDTO = new UserAuthDTO();
                officialAuthDTO.setIdentityType(IdentityType.WE_CHAT_OFFICIAL_ACCOUNT);
                officialAuthDTO.setUserId(userId);
                officialAuthDTO.setIdentifier(openId);
                officialAuthDTO.setCredential(openId);
                userAuthService.create(officialAuthDTO);
            }
        }else if(LoginSource.ALIPAY.equals(loginDTO.getSource())){
            UserAuthEntity alipayAuth = userAuthService.getByUserIdAndType(userId, IdentityType.ALI_PAY);
            if(Objects.isNull(alipayAuth)){
                String aliPayOpenId = getAliPayOpenId(loginDTO.getCode());
                UserAuthDTO officialAuthDTO = new UserAuthDTO();
                officialAuthDTO.setIdentityType(IdentityType.ALI_PAY);
                officialAuthDTO.setUserId(userId);
                officialAuthDTO.setIdentifier(aliPayOpenId);
                officialAuthDTO.setCredential(aliPayOpenId);
                userAuthService.create(officialAuthDTO);
            }
        }
        String token = this.login(phoneUserAuthDTO, request, response);
        return BaseResultFactory.produceResult(Code.SUCCESS, token);
    }

    private String getAliPayOpenId(String code){
        AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",
                aliPayProperty.getAppId(),aliPayProperty.getPrivateKey(),"json","GBK",
                aliPayProperty.getPublicKey(),"RSA2");
        AlipaySystemOauthTokenRequest request = new AlipaySystemOauthTokenRequest();
        request.setGrantType("authorization_code");
        request.setCode(code);
        try{
            AlipaySystemOauthTokenResponse response = alipayClient.execute(request);
            if(response.isSuccess()){
                return response.getUserId();
            }
            return null;
        }catch (Exception e){
            throw new BusinessException(Code.USER_NOT_AUTH_WE_CHAT);
        }

    }

    /**
     * 创建token
     *
     * @param userAuthDTO
     * @param request
     * @param response
     * @return
     */
    private String login(UserAuthDTO userAuthDTO, HttpServletRequest request, HttpServletResponse response) {
        Token token = new Token();
        token.setUserId(userAuthDTO.getUserId());
        token.setIdentityType(userAuthDTO.getIdentityType());
        // 设置过期时间
        long exp = System.currentTimeMillis() + businessConstant.TOKEN_VALID_TIME * 60000;
        token.setTimestamp(exp);
        String jwtStr = tokenFactory.createJwtWithSecret(token, userAuthDTO.getCredential());
        TokenUtil.setTokenToCookie(jwtStr, true, request, response);
        return jwtStr;
    }
    
    /**
     * 验证
     *
     * @param userAuthEntity
     * @param password       密码
     * @return 验证结果
     */
    private Code checkAccountAndPWD(UserAuthEntity userAuthEntity, String password) {
        UserEntity userEntity = userAuthEntity.getUser();

        if (BooleanUtils.isNotTrue(userEntity.getEnable())) {
            return Code.ACCOUNT_DISABLED;
        }

        // 验证是否处于锁定状态并且处于锁定时间内
        if ((userEntity.getStatus() == AccountStatus.LOCK.getCode()) &&
                DateUtil.isTwoTimeDeltaMinute(System.currentTimeMillis(), Long.valueOf(userEntity.getLockTime()), businessConstant.ACCOUNT_LOCK_MINUTE)) {
            return Code.ACCOUNT_STILL_LOCK;
        }

        // 验证是否处于锁定状态, 过了锁定时间，则改为有效
        if ((userEntity.getStatus() == AccountStatus.LOCK.getCode())) {
            userEntity.setStatus(AccountStatus.VALID.getCode());
        }

        // 验证密码
        String pwdMd5 = DigestUtils.md5Hex(password + BaseConstant.DEFAULT_PASSWORD_SALT);
        if (pwdMd5.equals(userAuthEntity.getCredential())) {
            return Code.SUCCESS;
        }

        // 验证错误次数
        if (userEntity.getErrorCount() >= businessConstant.PASSWORD_ERROR_ALLOW) {
            return Code.PASSWORD_ERROR_TIME_OVER;
        }

        return Code.ERROR_PASSWORD_ACCOUNT;
    }
}
